package gp;

import firing.BinaryNode;
import firing.ChangeAngleNode;
import firing.EnergyBranch;
import firing.FireNode;
import firing.FiringTree;
import firing.FlipFlopBranch;
import firing.LastFiredBranch;
import firing.Node;
import firing.UnaryNode;

/** Class that performs genetic manipulations of trees*/
public class Mutator {

    /** mutate random node. */
    public static void gMutate(FiringTree tree) {
	Node n = tree.getRandomNode();
	if (n.isUnaryNode()) {
	    mutateUnaryNode((UnaryNode)n);
	} else {
	    mutateBinaryNode((BinaryNode)n);
	}
    }
    
    /** Swaps random subtrees between the two trees */
    public static void gCrossover(FiringTree tree1, FiringTree tree2) {
	Node first = tree1.getRandomNode();
	Node second = tree2.getRandomNode();
	boolean firstUnary = first.isUnaryNode();
	boolean secondUnary = second.isUnaryNode();
	if (firstUnary && secondUnary) {
	    crossUnaryNodes((UnaryNode)first, (UnaryNode)second);
	} else if (!firstUnary && !secondUnary) {
	    crossBinaryNodes((BinaryNode)first, (BinaryNode)second);
	} else if (!firstUnary) {
	    crossMixedNodes((UnaryNode)second,(BinaryNode)first);
	} else {
	    crossMixedNodes((UnaryNode)first,(BinaryNode)second);
	}
    }
    
    /** Adds a subtree if node has no child, deletes it otherwise */
    private static void mutateUnaryNode(UnaryNode node) {
	if (Math.random() > .5 || node.getChild() == null) { //add/delete subtrees. Null check to avoid null pointer in replace with unary nodes bit
	    if (node.getChild() == null || Math.random() > .5) {//weights more heavily away from deleting nodes
		node.setChild(TreeGenerator.getRandomSubtree());
	    } else {
		node.setChild(null);
	    }
	} else {
	    if (node.getChild().isUnaryNode()) { //replace with unary nodes
		if (Math.random()>.5) {
		    UnaryNode replacement = new FireNode();
		    replacement.setChild(((UnaryNode)node.getChild()).getChild());
		    node.setChild(replacement);
		} else {
		    UnaryNode replacement = new ChangeAngleNode();
		    replacement.setChild(((UnaryNode)node.getChild()).getChild());
		    node.setChild(replacement);
		}
	    } else { //replace with binary nodes
		    int nodeChoice = (int)(Math.random()*3);
		    BinaryNode binaryReference = (BinaryNode) node.getChild();
		    BinaryNode replacement;
		    switch(nodeChoice) {
		    case 0: 
			replacement = new EnergyBranch();
			
			node.setChild(replacement);
			break;
		    case 1: 
			replacement = new FlipFlopBranch();
			node.setChild(replacement);
			break;
		    default:
			replacement = new LastFiredBranch();
			node.setChild(replacement);
			break;
		    }
		    replacement.setLeftChild(binaryReference.getLeftChild());
		    replacement.setRightChild(binaryReference.getRightChild());
		}
	}
    }
    
    /** Adds or deletes a subtree, or swaps a node's children */
    private static void mutateBinaryNode(BinaryNode node) {
	double choice = Math.random();
	if (choice < 0.33) {//swap children
	    Node temp = node.getLeftChild();
	    node.setLeftChild(node.getRightChild());
	    node.setRightChild(temp);
	} else if (choice < .66 ){//add/delete subtrees
	    if (Math.random() < 0.5) { //left child
		if (node.getLeftChild() == null || Math.random()<.5) {
		    node.setLeftChild(TreeGenerator.getRandomSubtree());
		} else {
		    node.setLeftChild(null);
		}
	    } else { //right child
		if (node.getRightChild() == null) {
		    node.setRightChild(TreeGenerator.getRandomSubtree());
		} else {
		    node.setRightChild(null);
		}
	    }
	} else { //change node type of one of the children
	    if (Math.random() < .5) {//change left child
		if (node.getLeftChild() == null) {
		    return;
		}
		if (node.getLeftChild().isUnaryNode()) { //replace with unary nodes
		    UnaryNode replacement;
		    if (Math.random()>.5) {
			replacement = new FireNode();
			node.setLeftChild(replacement);
		    } else {
			replacement = new ChangeAngleNode();
			node.setLeftChild(replacement);
		    }
		    replacement.setChild(((UnaryNode)node.getLeftChild()).getChild());
		    
		} else { //replace with binary nodes
		    BinaryNode binaryReference = (BinaryNode) node.getLeftChild();
		    BinaryNode replacement = TreeGenerator.getRandomBinaryNode();
		    replacement.setLeftChild(binaryReference.getLeftChild());
		    replacement.setRightChild(binaryReference.getRightChild());
		    node.setLeftChild(replacement);
		}
	    } else { //change right child. MY KINGDOM FOR FUNCTION POINTERS!
		if (node.getRightChild() == null) {
		    return;
		}
		if (node.getRightChild().isUnaryNode()) { //replace with unary nodes
		    UnaryNode replacement;
		    if (Math.random() > .5) {
			replacement = new FireNode();
		    } else {
			replacement = new ChangeAngleNode();
		    }
		    node.setRightChild(replacement);
		    replacement.setChild(((UnaryNode)node.getRightChild()).getChild());
		} else { //replace with binary nodes
		    BinaryNode binaryReference = (BinaryNode) node.getRightChild();
		    BinaryNode replacement = TreeGenerator.getRandomBinaryNode();
		    replacement.setLeftChild(binaryReference.getLeftChild());
		    replacement.setRightChild(binaryReference.getRightChild());
		    node.setRightChild(replacement);
		}
	    }
	}
    }
    
    private static void crossUnaryNodes(UnaryNode first, UnaryNode second) {
	Node temp = first.getChild();
	first.setChild(second.getChild());
	second.setChild(temp);
    }
    
    private static void crossBinaryNodes(BinaryNode first, BinaryNode second) {
	double decision = Math.random(); 
	if (decision < .25) { 		//swap first.left and second.left
	    Node temp = first.getLeftChild();
	    first.setLeftChild(second.getLeftChild());
	    second.setLeftChild(temp);
	} else if (decision < .5) {	//swap first.left and second.right
	    Node temp = first.getLeftChild();
	    first.setLeftChild(second.getRightChild());
	    second.setRightChild(temp);
	} else if (decision < .75) {	//swap first.right and second.left
	    Node temp = first.getRightChild();
	    first.setRightChild(second.getLeftChild());
	    second.setLeftChild(temp);
	} else {			//swap first.right and second.right
	    Node temp = first.getRightChild();
	    first.setRightChild(second.getRightChild());
	    second.setRightChild(temp);
	}
    }
    
    private static void crossMixedNodes(UnaryNode first, BinaryNode second) {
	if (Math.random() < .5) {
	    Node temp = first.getChild();
	    first.setChild(second.getLeftChild());
	    second.setLeftChild(temp);
	} else {
	    Node temp = first.getChild();
	    first.setChild(second.getRightChild());
	    second.setRightChild(temp);
	}
    }
}
